﻿#region namespaces

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using Db4objects.Db4o;
using Db4objects.Db4o.Activation;
using Db4objects.Db4o.Collections;
using ZOIL.DataBackend.Db4o.Annotations;
using ZOIL.DataModel;

#endregion

namespace ZOIL.DataBackend.Db4o
{
    public class Db4oCollection : BaseActivatable, IPersistableCollection, IRemoteUpdateable
    {
        #region fields

        [Transient] private ReaderWriterLockSlim _rwl;

        /// <summary>
        ///     Items synchronized via db4o
        /// </summary>
        private readonly ArrayList4<Tuple<IPersistable, IVisualProperties>> _synchronizedItems;

        /// <summary>
        ///     Items for INotifyCollectionChanged -> holds the exakt items like <see cref="_synchronizedItems" />, but without synchronization via db4o
        /// </summary>
        [Transient] private ObservableCollection<Tuple<IPersistable, IVisualProperties>> _items;

        public void OnCreated()
        {
            ViewFor.AddViewFor(typeof (Db4oCollection), new Dictionary<string, Type> {{"default", typeof (PersistableCollectionView)}});

            _rwl = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
            _items = new ObservableCollection<Tuple<IPersistable, IVisualProperties>>(_synchronizedItems);

            //On CollectionChanged: listeners hin und weg
//            PropertyChanged += (_, e) => Activate(ActivationPurpose.Write);
//            CollectionChanged += (_, e) => Activate(ActivationPurpose.Write);

            foreach (var t in _synchronizedItems)
            {
                Database.Instance.Objects.Activate(t.Item1, Database.UpdateDepth);
                Database.Instance.Objects.Activate(t.Item2, Database.UpdateDepth);
            }
        }

        #endregion

        #region ctor

        public Db4oCollection()
        {
            _synchronizedItems = new ArrayList4<Tuple<IPersistable, IVisualProperties>>();
            OnCreated();
        }

        #endregion

        #region Implementation of IEnumerable

        /// <summary>
        ///     Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        ///     A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<Tuple<IPersistable, IVisualProperties>> GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        /// <summary>
        ///     Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        ///     An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Implementation of ICollection<Tuple<IPersistable,IVisualProperties>>

        /// <summary>
        ///     Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <param name="item">
        ///     The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </param>
        /// <exception cref="T:System.NotSupportedException">
        ///     The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.
        /// </exception>
        public void Add(Tuple<IPersistable, IVisualProperties> item)
        {
            using (_rwl.WriteLock())
            {
                Database.Instance.StoreItem(item.Item1, item.Item2);
                _synchronizedItems.Add(item);
                _items.Add(item);
            }
            OnPropertyChanged("Count");
            Activate(ActivationPurpose.Write);
        }

        /// <summary>
        ///     Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">
        ///     The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.
        /// </exception>
        public void Clear()
        {
            if (Count <= 0)
            {
                return;
            }

            using (_rwl.WriteLock())
            {
                var arr = new Tuple<IPersistable, IVisualProperties>[Count];
                _synchronizedItems.CopyTo(arr, 0);
                _synchronizedItems.Clear();
                Database.Instance.Objects.Store(_synchronizedItems);
                foreach (var i in arr)
                {
                    Database.Instance.Objects.Delete(i);
                }
                _items.Clear();
            }
            OnPropertyChanged("Count");
            Activate(ActivationPurpose.Write);
            Database.Instance.CommitChanges();
        }

        /// <summary>
        ///     Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.
        /// </summary>
        /// <returns>
        ///     true if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false.
        /// </returns>
        /// <param name="item">
        ///     The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </param>
        public bool Contains(Tuple<IPersistable, IVisualProperties> item)
        {
            using (_rwl.ReadLock())
            {
                return _items.Contains(item);
            }
        }

        /// <summary>
        ///     Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1" /> to an
        ///     <see
        ///         cref="T:System.Array" />
        ///     , starting at a particular <see cref="T:System.Array" /> index.
        /// </summary>
        /// <param name="array">
        ///     The one-dimensional <see cref="T:System.Array" /> that is the destination of the elements copied from
        ///     <see
        ///         cref="T:System.Collections.Generic.ICollection`1" />
        ///     . The <see cref="T:System.Array" /> must have zero-based indexing.
        /// </param>
        /// <param name="arrayIndex">
        ///     The zero-based index in <paramref name="array" /> at which copying begins.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        ///     <paramref name="array" /> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///     <paramref name="arrayIndex" /> is less than 0.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        ///     The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1" /> is greater than the available space from
        ///     <paramref
        ///         name="arrayIndex" />
        ///     to the end of the destination <paramref name="array" />.
        /// </exception>
        public void CopyTo(Tuple<IPersistable, IVisualProperties>[] array, int arrayIndex)
        {
            using (_rwl.ReadLock())
            {
                _items.CopyTo(array, arrayIndex);
            }
        }

        /// <summary>
        ///     Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <returns>
        ///     true if <paramref name="item" /> was successfully removed from the
        ///     <see
        ///         cref="T:System.Collections.Generic.ICollection`1" />
        ///     ; otherwise, false. This method also returns false if
        ///     <paramref
        ///         name="item" />
        ///     is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </returns>
        /// <param name="item">
        ///     The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </param>
        /// <exception cref="T:System.NotSupportedException">
        ///     The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.
        /// </exception>
        public bool Remove(Tuple<IPersistable, IVisualProperties> item)
        {
            bool wasRemoved;
            using (_rwl.WriteLock())
            {
//                var i = _synchronizedItems.FirstOrDefault(t => t.Item1.DatabaseId == item.Item1.DatabaseId);

                wasRemoved = _synchronizedItems.Remove(item);
                if (wasRemoved)
                {
                    _items.Remove(item);
                }
            }

            if (wasRemoved)
            {
                OnPropertyChanged("Count");
                Activate(ActivationPurpose.Write);
            }
            return wasRemoved;
        }

        /// <summary>
        ///     Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <returns>
        ///     The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </returns>
        public int Count
        {
            get { return _synchronizedItems.Count; }
        }

        /// <summary>
        ///     Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.
        /// </summary>
        /// <returns>
        ///     true if the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only; otherwise, false.
        /// </returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        #endregion

        #region Implementation of IPersistsVisualProperties

        public IPersistable GetPersistable(long id)
        {
            return GetPersistableAndVisualProperties(id).Item1;
        }

        public IVisualProperties GetVisualProperties(long id)
        {
            return GetPersistableAndVisualProperties(id).Item2;
        }

        public Tuple<IPersistable, IVisualProperties> GetPersistableAndVisualProperties(long id)
        {
            using (_rwl.ReadLock())
            {
                return _items.Single(t => t.Item1.DatabaseId == id);
            }
        }

        public Tuple<IPersistable, IVisualProperties> GetPersistableAndVisualPropertiesIfExisting(long id)
        {
            using (_rwl.ReadLock())
            {
                return _items.SingleOrDefault(ip => ip.Item1.DatabaseId == id);
            }
        }

        public void Add(IPersistable p, IVisualProperties prop)
        {
            Add(Tuple.Create(p, prop));
        }

        #endregion

        #region Implementation of INotifyCollectionChanged

        public event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add { _items.CollectionChanged += value; }
            remove { _items.CollectionChanged -= value; }
        }

        #endregion

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        private void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            //nichts machen
        }

        public string BackendName { get { return "Db4o"; } }

        public bool Remove(long id)
        {
            var item = GetPersistableAndVisualPropertiesIfExisting(id);
            if (item != null)
            {
                return Remove(item);
            }
            return false;
        }

        #endregion

        #region Implementation of IRemoteUpdateable

        public void OnRemoteUpdated()
        {
            //Noch überprüfen ob was an der liste geändert wurde
            using (_rwl.WriteLock())
            {
                if (_synchronizedItems.Count == 0 && _items.Count == 0)
                {
                    //nichts machen
                }
                else if (_synchronizedItems.Count == 0 && _items.Count > 0)
                {
                    _items.Clear();
                }
                else
                {
                    var remoteRemoved = _items.Except(_synchronizedItems).ToArray();
                    var remoteAdded = _synchronizedItems.Except(_items).ToArray();

                    foreach (var t in remoteRemoved)
                    {
                        _items.Remove(t);
                    }
                    foreach (var t in remoteAdded)
                    {
                        //greift sonst auf items zu die noch nicht synchronisiert wurden
                        //busy waiting, aber sollte nur kurz der fall sein -> weniger kostenintensiv als sleep oder anderes
                        while (t.Item1 == null)
                        {
                        }
                        while (t.Item2 == null)
                        {
                        }
                        Database.Instance.Objects.Activate(t.Item1, Database.UpdateDepth);
                        Database.Instance.Objects.Activate(t.Item2, Database.UpdateDepth);

                        _items.Add(t);
                    }
                }
            }

            OnPropertyChanged("Count");
        }

        #endregion
    }
}